/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __BAMP_MESSAGE_LOOP_HPP__
#define __BAMP_MESSAGE_LOOP_HPP__

#include "message_ack.hpp"
#include "message_target.hpp"
#include "async_loop.hpp"

#include <bamp/message_ptr.hpp>

#include <boost/thread.hpp>
#include <boost/interprocess/sync/interprocess_semaphore.hpp>

#include <tr1/functional>

#include <queue>
#include <set>


namespace bamp
{

    typedef std::tr1::function<void(MessagePtr message)> MessageFunction;


    /**
     * Message loop. Messages handed to a message loop through
     * handle() generate a call to any number of functions or
     * function-like objects registered through connect(). All the
     * messages are handled in the same thread.
     *
     * Message types can be priorized. Messages with a lower priority level
     * are handled first but messages with equal priority levels are handled
     * in the order they were sent.
     */
    class MessageLoop : public AsyncLoop, public MessageTarget {
    
    public:

        /**
         * Constructor. Initializes the thread without running it yet.
         * @param nPriorities number of priority levels
         */
        explicit MessageLoop(const int nPriorities = 1);

        /**
         * Destructor. Detaches the thread from any target and stops it
         * if it is running.
         */
        virtual ~MessageLoop();


        /**
         * Get the number of messages that have not yet been processed.
         * @return number of messages pending
         */
        int messagesPending();

        /**
         * Get the number of messages that have not yet been processed.
         * @param priority priority level
         * @return number of messages pending
         */
        int messagesPending(const int priority);

        /**
         * Send a message by calling all the handlers registered for it
         * using connect(). The message acknowledgement object will be
         * notified when the message has finished being handled.
         * @param message the message
         * @param ack     message acknowledgement object
         */
        virtual void send(const Message& message, MessageAck ack);

        /**
         * Handle a message by calling all the handlers registered for its
         * type using connect().
         */    
        virtual MessageAck send(const Message& message);

        /**
         * Register an handler for a message.
         * @param name the message name
         * @param func the function or function-like object to call whenever
         *             a message of that type is sent
         */
        void connect(const std::string& name, const MessageFunction& func);

        /**
         * Register a member function handler for a message
         * @param name   the message name
         * @param func   the function
         * @param object the object to call the function on
         */
        template<typename T1, typename T2>
        void connect(const std::string& name, T1 func, T2 object)
        {
            connect(name,
                    std::tr1::bind(func, object, std::tr1::placeholders::_1));
        }

        /**
         * Register a default handler for messages.
         * @param func the function or function-like object to call whenever
         *             a message of that type is sent
         */
        void setDefault(const MessageFunction& func);

        /**
         * Register a member function handler for a message
         * @param func   the function
         * @param object the object to call the function on
         */
        template<typename T1, typename T2>
        void setDefault(T1 func, T2 object)
        {
            setDefault(std::tr1::bind(func, object, std::tr1::placeholders::_1));
        }

        /**
         * Unregister any handler associated with a message.
         * @param name message name
         */
        void disconnect(const std::string& name);

        /**
         * Set a message's priority level.
         * @param priority priority level
         */
        void setPriority(const std::string& name, const int priority);
    
        /**
         * Freeze a priority level so that messages in the corresponding queue
         * stop being processed.
         * @param priority priority level
         */
        void freezePriority(const int priority);

        /**
         * Thaw a priority level so that messages in the corresponding queue
         * get processed as usual.
         * @param priority priority level
         */
        void thawPriority(const int priority);

        /**
         * Flush all messages of a given priority level.
         * @param priority priority level
         */
        void flushPriority(const int priority);


        /**
         * Get a lock on the thread. As long as the lock returned exists
         * and is locked, no messages will be processed.
         */
        boost::mutex::scoped_lock lock();


    private:

        virtual bool preRun();
        virtual bool postRun();
        virtual bool iteration();
        virtual bool preStop();
        virtual void postStop();


    private:

        typedef std::multimap<std::string, MessageFunction> HandlerMap;
        typedef std::multimap<std::string, int>             PriorityMap;
        typedef std::pair<MessagePtr, MessageAck>           MessagePair;
        typedef std::queue<MessagePair>                     MessageQueue;
        typedef std::set<MessageTarget*>                    TargetSet;
        typedef boost::interprocess::interprocess_semaphore Semaphore;


        HandlerMap      m_handlers;       /// Message type => handler map
        MessageFunction m_defaultHandler; /// Default handler
        boost::mutex    m_handlersMutex;  /// Mutex for m_handlers

        PriorityMap   m_priorities;    /// Message type => priority level map
        int           m_nPriorities;   /// Number of priority levels
        bool*         m_prioFrozen;    /// Whether priority levels are frozen
        MessageQueue* m_messages;      /// Message queue
        boost::mutex  m_messagesMutex; /// Mutex for m_messages
        Semaphore     m_messagesSem;   /// Semaphore for message availability

        TargetSet     m_targets;       /// Targets the thread is attached to
        boost::mutex  m_targetsMutex;  /// Mutex for m_targets

        boost::mutex  m_userMutex;     /// Mutex for user functions

    }; // class MessageLoop

} // namespace bamp

#endif // __BAMP_MESSAGE_LOOP_HPP__
